Skip to content

crypto: add signDigest/verifyDigest and Ed25519ctx support#62345

Open
panva wants to merge 2 commits intonodejs:mainfrom
panva:sig-prehash
Open

crypto: add signDigest/verifyDigest and Ed25519ctx support#62345
panva wants to merge 2 commits intonodejs:mainfrom
panva:sig-prehash

Conversation

@panva
Copy link
Member

@panva panva commented Mar 19, 2026

notable-change PRs with changes that should be highlighted in changelogs. 👇

Adds crypto.signDigest() and crypto.verifyDigest(), one-shot functions that sign/verify a pre-computed hash digest directly, without hashing internally.


const digest = crypto.createHash('sha256').update(data).digest();

const sig = crypto.signDigest('sha256', digest, privateKey);
const ok = crypto.verifyDigest('sha256', digest, publicKey, sig);

Supports RSA (PKCS#1 v1.5, PSS), ECDSA, DSA, Ed25519, Ed448, and ML-DSA (external mu).

Also adds Ed25519 context string support to crypto.sign(), crypto.verify() as well as the new methods.

Resolves: #60263

Pre-hash variants of Ed25519 and Ed448 as well Ed25519 context is defined in RFC8032

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/crypto
  • @nodejs/security-wg

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Mar 19, 2026
@panva panva added crypto Issues and PRs related to the crypto subsystem. semver-minor PRs that contain new features and should be released in the next minor version. labels Mar 19, 2026
@panva panva marked this pull request as ready for review March 19, 2026 22:04
@panva panva added the notable-change PRs with changes that should be highlighted in changelogs. label Mar 19, 2026
@github-actions
Copy link
Contributor

The notable-change PRs with changes that should be highlighted in changelogs. label has been added by @panva.

Please suggest a text for the release notes if you'd like to include a more detailed summary, then proceed to update the PR description with the text or a link to the notable change suggested text comment. Otherwise, the commit will be placed in the Other Notable Changes section.

@codecov
Copy link

codecov bot commented Mar 19, 2026

Codecov Report

❌ Patch coverage is 84.84848% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.70%. Comparing base (abff716) to head (c1342a1).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/crypto/crypto_sig.cc 78.99% 16 Missing and 9 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #62345      +/-   ##
==========================================
- Coverage   91.60%   89.70%   -1.91%     
==========================================
  Files         337      676     +339     
  Lines      140745   206807   +66062     
  Branches    21807    39616   +17809     
==========================================
+ Hits       128933   185509   +56576     
- Misses      11588    13444    +1856     
- Partials      224     7854    +7630     
Files with missing lines Coverage Δ
lib/crypto.js 93.04% <100.00%> (+0.07%) ⬆️
lib/internal/crypto/sig.js 98.03% <100.00%> (+2.30%) ⬆️
src/crypto/crypto_sig.h 63.63% <ø> (ø)
src/crypto/crypto_sig.cc 73.14% <78.99%> (ø)

... and 457 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ChALkeR
Copy link
Member

ChALkeR commented Mar 20, 2026

Not entirely certain how this works, will test

@panva
Copy link
Member Author

panva commented Mar 20, 2026

Not entirely certain how this works, will test

regular

crypto.sign('sha256', data, key) uses OpenSSL's EVP_DigestSign API, it hashes the input internally, then signs the resulting digest. One call does both steps.

prehashed

crypto.signDigest('sha256', digest, key) uses the lower-level EVP_PKEY_sign API, it receives an already-computed digest and goes straight for the signature. No hashing happens inside OpenSSL.

Even though the digest is already computed, OpenSSL needs to know which digest was used:

  • RSA PKCS#1 v1.5, The signed payload isn't just the hash. It's DigestInfo || hash, where DigestInfo is an ASN.1 prefix encoding the digest algorithm OID. Without the algorithm, OpenSSL can't construct DigestInfo, and verifiers wouldn't know what they're looking at.

  • RSA-PSS, digest algorithm feeds into both the MGF1 mask generation and the internal padding hash. OpenSSL needs it to build the PSS padding structure correctly.

  • ECDSA / DSA, digest bytes are interpreted as a big integer (truncated to the curve order bit-length for ECDSA). EVP_PKEY_CTX_set_signature_md is still required by OpenSSL for consistency, even though the actual ECDSA/DSA math only operates on the raw digest bytes. This is also why signing a SHA-512 digest with P-256 (32-byte order) works fine, the integer just gets truncated.

Other key type dependant cases:

  • Ed25519ph / Ed448ph, have fixed prehash requirements (SHA-512 for Ed25519ph, SHAKE256(m, 64) for Ed448ph)

  • ML-DSA, has a fixed prehash construction, external mu per FIPS 204

  • SLH-DSA (Pure SLH-DSA), not supported

  • HashSLH-DSA, not supported

break;
#else
if (can_throw)
crypto::CheckThrow(env, SignBase::Error::PrehashUnsupported);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conflicts with #62348, right?

Copy link
Member Author

@panva panva Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is what lead me to open #62348. To be dealt with depending in whichever is to land second.

@panva panva added author ready PRs that have at least one approval, no pending requests for changes, and a CI started. request-ci Add this label to start a Jenkins CI on a PR. labels Mar 20, 2026
@panva
Copy link
Member Author

panva commented Mar 21, 2026

(nothing changed about the implementation, just extended test coverage)

@panva panva removed the request-ci Add this label to start a Jenkins CI on a PR. label Mar 22, 2026
bool EVPKeyCtxPointer::setSignatureMd(const Digest& md) {
if (!ctx_ || !md) return false;
return EVP_PKEY_CTX_set_signature_md(ctx_.get(), md.get()) == 1;
}
Copy link
Member

@jasnell jasnell Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a PR adding these into node/ncrypto yet? Preference would be to land these there first.

* `callback` {Function}
* `err` {Error}
* `signature` {Buffer}
* Returns: {Buffer} if the `callback` function is not provided.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really not a big fan of polymorphic returns. I'd prefer separate signDigest (async callback) and signDigestSync (sync returning Buffer) methods than one method that is either sync or async depending on how it's called.

Copy link
Member Author

@panva panva Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

¯\_(ツ)_/¯

### `crypto.decapsulate(key, ciphertext[, callback])`
### `crypto.diffieHellman(options[, callback])`
### `crypto.encapsulate(key[, callback])`
### `crypto.randomBytes(size[, callback])`
### `crypto.randomInt([min, ]max[, callback])`
### `crypto.sign(algorithm, data, key[, callback])`
### `crypto.verify(algorithm, data, key, signature[, callback])`

Especially because of the last two i'm going to stick to this "convention" here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I've long despised this pattern and would really prefer that we not add to it. I wouldn't mind going back and revisiting these existing ones to add the Sync variants and deprecating the optional callback

Copy link
Member Author

@panva panva Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[, callback] appears in 77 distinct methods throughout various builtins, it is not just node:crypto. The pattern works and is wildly depended on for the existing methods, I disagree with changing it for the "same but slightly different" digest based sign/verify.

Furthermore a deprecation in these would be completely upside down since a sync version is the one without the currently optional callback. So a new method would have to be *Async which would be totally new in any builtin. Unless you're suggesting deprecating the default non-callback mode of these methods which I'm hoping you're not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I know the pattern is not limited to node:crypto... that doesn't mean I have to be a fan of it ;-)

What I would prefer these to be is something like

crypto.decapsulate(key, ciphertext, callback); // ok, always async
crypto.decapsulate(key, ciphertext);  // sync, still works but missing callback deprecated
crypto.decapsulateSync(ke, ciphertext);  // ok, always sync
// ...

Rinse-repeat for each of the places where we see this pattern today.

I'm not going to block on it, just saying that I really wish we wouldn't keep perpetuating this maybe-sync-maybe-async pattern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author ready PRs that have at least one approval, no pending requests for changes, and a CI started. crypto Issues and PRs related to the crypto subsystem. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. notable-change PRs with changes that should be highlighted in changelogs. semver-minor PRs that contain new features and should be released in the next minor version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crypto cant sign/verify prehashed inputs

5 participants